Adding Interactivity
インタラクティビティの追加
画面上の一部はユーザーの入力に応じて更新されます。
例えば、画像ギャラリーの画像をクリックすると、表示されている画像が切り替わる場合があります。Reactでは、時間の経過とともに変化するデータを「state」と呼びます。任意のコンポーネントにstateを追加し、必要に応じて更新できます。この章では、ユーザーとのインタラクションを処理し、stateを更新し、時間とともに異なる出力を表示するコンポーネントの書き方を学びます。 この章の内容
ユーザーによって開始されるイベントの処理方法
コンポーネントにstateを追加して情報を「記憶」させる方法
ReactがUIを二段階で更新する方法
stateが変更後すぐに更新されない理由
複数のstate更新をキューに入れる方法
state内のオブジェクトを更新する方法
state内の配列を更新する方法
イベントに応答する
Reactでは、JSXにイベントハンドラーを追加できます。イベントハンドラーは、クリック、ホバー、フォーム入力へのフォーカスなどのユーザーインタラクションに応じて実行される独自の関数です。
例えば、<button>のような組み込みコンポーネントは、onClickなどのブラウザイベントのみをサポートします。しかし、独自のコンポーネントを作成し、アプリケーション固有の名前を持つイベントハンドラープロップを渡すこともできます。
jsx
code: ts
export default function App() {
return (
<Toolbar
onPlayMovie={() => alert('Playing!')}
onUploadImage={() => alert('Uploading!')}
/>
);
}
function Toolbar({ onPlayMovie, onUploadImage }) {
return (
<div>
<Button onClick={onPlayMovie}>
Play Movie
</Button>
<Button onClick={onUploadImage}>
Upload Image
</Button>
</div>
);
}
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
イベントハンドラーの追加方法について学ぶには、「Responding to Events」を参照してください。
State:コンポーネントのメモリー
コンポーネントは、インタラクションの結果として画面上の表示を変更する必要があります。フォームに入力すると入力フィールドが更新され、画像カルーセルの「次へ」をクリックすると表示される画像が変更され、「購入」をクリックすると商品がショッピングカートに追加されるなどです。Reactでは、このようなコンポーネント固有の記憶を「state」と呼びます。
useStateフックを使用してコンポーネントにstateを追加できます。フックは、Reactの機能(stateもその一部)をコンポーネントで使用できる特別な関数です。useStateフックはstate変数を宣言し、初期stateを受け取り、現在のstateとstateを更新するための関数のペアを返します。
jsx
code: ts
以下は、クリック時にstateを使用して更新する画像ギャラリーの例です:
jsx
code: ts
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const hasNext = index < sculptureList.length - 1;
function handleNextClick() {
if (hasNext) {
setIndex(index + 1);
} else {
setIndex(0);
}
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureListindex; return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
</>
);
}
stateを記憶してインタラクションに応じて更新する方法について学ぶには、「State: A Component’s Memory」を参照してください。
レンダリングとコミット
コンポーネントが画面に表示される前に、Reactによってレンダリングされます。このプロセスの手順を理解することで、コードの実行方法やその動作を説明する際に役立ちます。
コンポーネントを料理人、Reactをウェイターに例えると、Reactは顧客からの注文を受けてコンポーネントにリクエストを送ります。このUIのリクエストと提供のプロセスには3つのステップがあります:
レンダリングのトリガー(注文を厨房に届ける)
コンポーネントのレンダリング(注文を準備する)
DOMへのコミット(注文をテーブルに置く)
レンダリングとコミットのライフサイクルについて学ぶには、「Render and Commit」を参照してください。
Stateのスナップショット
通常のJavaScript変数とは異なり、Reactのstateはスナップショットのように動作します。stateを設定しても既存のstate変数は変更されず、再レンダリングがトリガーされます。
jsx
code: ts
console.log(count); // 0
setCount(count + 1); // 1で再レンダリングをリクエスト
console.log(count); // まだ0!
これにより、微妙なバグを避けることができます。
以下はチャットアプリの一例です。「Send」を押してから受信者をBobに変更した場合、5秒後にアラートに表示される名前は誰でしょう?
jsx
code: ts
import { useState } from 'react';
export default function Form() {
function handleSubmit(e) {
e.preventDefault();
setTimeout(() => {
alert(You said ${message} to ${to});
}, 5000);
}
return (
<form onSubmit={handleSubmit}>
<label>
To:{' '}
<select
value={to}
onChange={e => setTo(e.target.value)}>
<option value="Alice">Alice</option>
<option value="Bob">Bob</option>
</select>
</label>
<textarea
placeholder="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
}
stateがイベントハンドラー内で「固定」されて変更されない理由について学ぶには、「State as a Snapshot」を参照してください。
一連のstate更新のキューイング
このコンポーネントにはバグがあります。「+3」をクリックしてもスコアが一回しか増えません。
jsx
code: ts
import { useState } from 'react';
export default function Counter() {
function increment() {
setScore(score + 1);
}
return (
<>
<button onClick={() => increment()}>+1</button>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
<h1>Score: {score}</h1>
</>
)
}
これは、stateの設定が新しい再レンダリングをリクエストするだけで、既存のコード内では変更されないためです。この問題は、stateを設定する際にアップデータ関数を渡すことで解決できます。例えば、setScore(score + 1)をsetScore(s => s + 1)に置き換えると、「+3」ボタンが正しく動作します。
jsx
code: ts
import { useState } from 'react';
export default function Counter() {
function increment() {
setScore(s => s + 1);
}
return (
<>
<button onClick={() => increment()}>+1</button>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
<h1>Score: {
<h1>Score: {score}</h1>
</>
)
}
stateの更新をキューイングする方法について学ぶには、「Queueing a Series of State Updates」を参照してください。
state内のオブジェクトの更新
stateは任意のJavaScript値を保持できますが、React stateに保持されているオブジェクトや配列を直接変更してはいけません。代わりに、オブジェクトや配列を更新する際には新しいものを作成する(または既存のものをコピーする)必要があります。その後、stateをそのコピーに設定します。
通常、オブジェクトや配列を変更するためには、スプレッド構文(...)を使用してコピーします。例えば、ネストされたオブジェクトを更新する場合は次のようになります:
jsx
code: ts
import { useState } from 'react';
export default function Form() {
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
}
});
function handleNameChange(e) {
setPerson({
...person,
name: e.target.value
});
}
function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}
function handleCityChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
city: e.target.value
}
});
}
return (
<form>
<label>
Name:
<input
value={person.name}
onChange={handleNameChange}
/>
</label>
<label>
Title:
<input
value={person.artwork.title}
onChange={handleTitleChange}
/>
</label>
<label>
City:
<input
value={person.artwork.city}
onChange={handleCityChange}
/>
</label>
</form>
);
}
オブジェクトのコピーが面倒な場合、Immerのようなライブラリを使用してコードを簡素化できます。
オブジェクトを正しく更新する方法について学ぶには、「Updating Objects in State」を参照してください。
state内の配列の更新
配列もstateに保存できる変更可能なJavaScriptオブジェクトの一種ですが、オブジェクトと同様に直接変更せず、コピーを作成してからstateをそのコピーに設定する必要があります。
jsx
code: ts
import { useState } from 'react';
const initialList = [
{ id: 0, title: 'Big Bellies', seen: false },
{ id: 1, title: 'Lunar Landscape', seen: false },
{ id: 2, title: 'Terracotta Army', seen: true },
];
export default function BucketList() {
function handleToggle(artworkId, nextSeen) {
setList(list.map(artwork => {
if (artwork.id === artworkId) {
return { ...artwork, seen: nextSeen };
} else {
return artwork;
}
}));
}
return (
<>
<h1>Art Bucket List</h1>
<h2>My list of art to see:</h2>
<ItemList artworks={list} onToggle={handleToggle} />
</>
);
}
function ItemList({ artworks, onToggle }) {
return (
<ul>
{artworks.map(artwork => (
<li key={artwork.id}>
<label>
<input
type="checkbox"
checked={artwork.seen}
onChange={e => onToggle(artwork.id, e.target.checked)}
/>
{artwork.title}
</label>
</li>
))}
</ul>
);
}
配列のコピーが面倒な場合、Immerのようなライブラリを使用してコードを簡素化できます。
配列を正しく更新する方法について学ぶには、「Updating Arrays in State」を参照してください。
次は何を学ぶべきか?
イベントに応答する方法について学び始めるには、「Responding to Events」に進んでください。
また、これらのトピックに既に精通している場合は、「Managing State」について読んでみてください。